
package javaapplication1;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.PriorityQueue;
import java.util.Scanner;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.MatchResult;

 /*
 * the Commands class - from slides
 */
public class Commands {

    // in the Simulation class i got the all the robots in an hashmap
    private Simulation sim;
    // this haspmap store all the various commands
    private HashMap<String,Command> hm ;
    // the robot factory is a hashmap that creates robots with a given type
    private RobotFactory rf ;
    // the Boxes hashmap - this class holds all boxes
    private Boxes bx;
    // the dir object lists all file and folders and enter them in an array
    // fashion to argg array
    private Scanner scn;
    private MatchResult result ;
    private Programs pro;

    private void add(Command c){
        hm.put(c.getKey(), c);
    }

    
    // commands Ctor
    public Commands(Simulation s){
        // reset the environment objects
        this.sim=s;
        rf = new RobotFactory();
        bx = new Boxes();
        hm = new HashMap<String,Command>();
        pro = new Programs();
     
        // adding commands for the hashmap
        add(new Move());
        add(new Init());
        add(new Load());
        add(new Simulate());
        add(new Get());
        add(new Exit());
        add(new Files());
        add(new List());
        add(new PutBox());
        add(new Assign());
 }
      public Commands(Simulation s,Boxes b){
        // reset the environment objects
        this.sim=s;
        rf = new RobotFactory();
        this.bx = b;
        hm = new HashMap<String,Command>();
        pro = new Programs();

        // adding commands for the hashmap
        add(new Move());
        add(new Init());
        add(new Load());
        add(new Simulate());
        add(new Get());
        add(new Exit());
        add(new Files());
        add(new List());
        add(new PutBox());
        add(new Assign());
 }
 // The decoder function is resposable for creating the match group
 // it returens flase if the cmd string not formated correctly (format)
     public boolean decoder(String cmd, String format)
     {
         if(cmd.matches(format)){// if the command we got fits the correct format
             scn=new Scanner(cmd);
             scn.findInLine(format);// scans according to the format
             result = scn.match();
             return true;
         }
         return false;
     }
// the Robot comparator
    public class RobotComparator implements Comparator<Robot>{
         public int compare(Robot r1, Robot r2)
         {
             if  (r1.getDistance() > r2.getDistance())
                 return 1;
             else if (r1.getDistance() < r2.getDistance())
                 return -1;
             else
                 return 0;

         }
     }
/** each new command is added as a new class that implements Command interface

 * Init Command :
* the Init creates a new RV1/RV2 robot.
* use the following format to do so :
* "init type [RV1,RV2] at X Y"
*/
    private class Init implements Command {
        String format="Init type (\\w+) at ([-]*\\d+) ([-]*\\d+) named (\\w+)[ with ]*(\\w*+)";
        
        // return the first word of the command as the key
        public String getKey(){
            return "Init";
        }
        public String getFormat(){return format;}

        // the decoder function check that the format is correct if not it returens false
        public void run() throws Exception
        {
            try {
                String type2 = null;
                // get the variables
                String type = result.group(1);
                int xpos = Integer.parseInt(result.group(2));
                int ypos = Integer.parseInt(result.group(3));
                String name = result.group(4);
                // case that the robot name already exists
                // if it does throw exception
                if (sim.getRobot(name) != null) {
                    throw new Exception("Error: Robot Name Already Exists");
                }
                if (result.group(5).contains("a")) {
                    type2 = result.group(5);
                } else if (type2 != null) {
                    // if the addition that was entered from the user does not exist
                    System.out.println("Error: no such addition.");
                    return;
                }
                // create temp variables
                Position tempos = new Position(xpos, ypos);
                // create the robot
                Robot temprobot = rf.createRobot(type, type2);
                if (temprobot == null) {
                    System.out.println("Error: no such robot type.");
                    return;
                }
                temprobot.setName(name);
                temprobot.setStartingPosition(tempos);
                // enter a robot to the robots hashmap
                sim.setRobot(temprobot, name);
                // user output tranferd to creation pahse
                //  String TTTT = sim.getRobot(name).getType();
                // return the robot type in a Decorator Pattern fashion
                //  System.out.println("new "+TTTT+" at "+tempos+" named "+name);
            } catch (Exception ex3) { throw ex3;}
     
        }
    }
/** Move Command :
*  the move command only changes the heading and speed of a robot
* without moving him.
* use the following format to do so :
* " move INDEX speed S heading H"
* INDEX is an index from an array of Robots
*/
    private class Move implements Command{
        String format="Move (\\w+) speed ([-]*\\d+) heading ([-]*\\d+)";

        // return the first word of the command as the key
        public String getKey(){ return "Move";}
        public String getFormat(){return format;}
        public void run()
        {
        // get the variables
            String name=scn.match().group(1);
            int speed=Integer.parseInt(scn.match().group(2));
            int heading=Integer.parseInt(scn.match().group(3));
            // do the required action
            if (sim.getRobot(name)==null){
                System.out.println("Error: no such robot with this name.");
                return; }
                sim.getRobot(name).move(speed, heading);
        }
    }
/** Simulate Command :
* The Simulate changes the Robot location depending on the heading and speed
* that was previously initialed
* use the following format to do so :
* "simulate INDEX K steps"
* INDEX is an index from an array of Robots
*
*    // the old Simulate Command
*    /*private class Simulate implements Command{
*        String format="Simulate (\\w+) (\\d+) steps";
*
*        // return the first word of the command as the key
*        public String getKey(){ return "Simulate";}
*        public String getFormat(){return format;}
*        public void run() {
*            // get the variables
*            String name=scn.match().group(1);
*            int counter=Integer.parseInt(scn.match().group(2));
*            if (sim.getRobot(name)==null){
*               System.out.println("Error: no such robot with this name.");
*                return; }
*            // save the old position in pre (this is for the get distance method)
*            sim.getRobot(name).setPre(sim.getRobot(name).getStartPosition());
*            // do the required action
*            while (counter>0){
*                sim.getRobot(name).act();
*               counter--;
*            }
*        }
*    }
     */
    // the new Simulate Command
    private class Simulate implements Command{
        String format="Simulate (\\d+) seconds";

        // return the first word of the command as the key
        public String getKey(){ return "Simulate";}
        public String getFormat(){return format;}
        public void run() {
            try {
                // get the variables
                int period = Integer.parseInt(scn.match().group(1));
                pro.run(period);

            } catch (InterruptedException ex) {
                Logger.getLogger(Commands.class.getName()).log(Level.SEVERE, null, ex);
            }

        }
    }
    
/** Get Command :
* The Get returns a Robot from array of Robots
* use the following format to do so :
* "get INDEX position"
* INDEX is an index from an array of Robots
*/
    private class Get implements Command{
        String format="Get (\\w+) position";
        // this is the new get with support for boxes and robots
        // return the first word of the command as the key
        public String getKey(){ return "Get";}
        public String getFormat(){return format;}
        public void run() {
            int flagbox = 1, flagrobot = 1 ;
            // 00 means ERROR
            // 01 means BOX
            // 10 means Robot
            // 11 not supported

            String name=scn.match().group(1);
            if (sim.getRobot(name)==null){
                //System.out.println("Error: no such robot with this name.");
                flagrobot = 0;
                //return;
            }
            if (bx.getBox(name)==null)
            {
                flagbox = 0 ;
                //System.out.println("Error: no such robot with this name.");
                //return;
            }
            if ((flagbox == 0 ) && (flagrobot==0))
            {
                System.out.println("Error: no such robot with this name.");
                return;
            }
            else if ((flagbox == 0 ) && (flagrobot==1))
            {
            // user output
            System.out.println("robot "+name+" is at "
                +sim.getRobot(name).getCurrentPosition());
            }
            else if ((flagbox == 1 ) && (flagrobot==0))
            {
            // user output
            System.out.println("box "+name+" is at "
                +bx.getBox(name).getPosition());
            }
        }
    }
/** Exit Command :
* The exit terminate the program in and return status 0
* use the following format to do so :
* "exit"
*/
    private class Exit implements Command{
        String format="Exit";

        public String getKey(){ return "Exit";}
        public String getFormat(){return format;}
        public void run(){
            System.out.println("bye.");
            System.exit(0);
        }


    }
/**
 * The Files command list all files in current directory (".")
 * file is marked with [f] and folder with [d]
 * use "Load" command to use the files
 */
    private class Files implements Command{
        String format="Files";
        FileLister _fileslist  = new FileLister();
        File[] _list ;
        int i;

        public String getKey(){ return "Files";}
        public String getFormat(){return format;}


        public void run()
        {
            i = 0;
            _list = _fileslist.getFiles();
            while ( i < _list.length) {
                System.out.println("\t[f] "+_list[i].getName());
                i++;
            }
            i = 0; 
            _list = _fileslist.getDir();
            while ( i < _list.length) {
                System.out.println("\t[d] "+_list[i].getName());
                i++;
            }
        }

    }
/**
 * The Load command get a file and do the command that are entered in the file
 * one by one
 */
    private class Load implements Command{
        String format="Load (.+)";
       

        public String getKey(){ return "Load";}
        public String getFormat(){return format;}
        

        public void run() throws Exception
        {
            try {
                String line;
                String name = scn.match().group(1);
                if (!new File(name).exists())
                    System.out.println("Error: file not found.");
                else
                {
                    BufferedReader br = new BufferedReader(new FileReader(name));
                    while ((line = br.readLine()) != null) {
                        System.out.println(">> "+line);
                        runCommand(line);
                    }
                }
            }
            catch (IOException ex) {
                Logger.getLogger(Commands.class.getName()).log(Level.SEVERE, null, ex);

            }
            catch (Exception ex2) {throw ex2;}

         }
    }
/**
 * The List command list all robots in a sorted fashion depends on their distance
 * from the (0,0)
 * 
 */
    private class List implements Command{
        String format="List robots by (\\w+)";
        
        public String getKey(){ return "List";}
        public String getFormat(){return format;}

        public void run() {

            String sorttype = scn.match().group(1);
            String name;
            int i = 1;


            if (sorttype.equalsIgnoreCase("name"))
            {

                ArrayList<Robot> arr = new ArrayList<Robot>();
                arr.addAll(sim.getHashmap());
                Collections.sort(arr);
                Iterator<Robot> it = arr.iterator();
                while (it.hasNext()){
                    System.out.println(i+". "+it.next().getName());
                    i++;
                }
            }
            else if (sorttype.equalsIgnoreCase("distance"))
            {
                
                PriorityQueue<Robot> pr = new PriorityQueue<Robot>(10,new RobotComparator());
                pr.addAll(sim.getHashmap());
                Iterator<Robot> it = pr.iterator();
                while (it.hasNext()){
                    name = it.next().getName();
                    System.out.println(i+". "+name+" distance "+sim.getRobot(name).getDistance());
                    i++;
                }
                
            }
            else
            {
                System.out.println("Error: command not found.");
            }
            
            
        }
        
        

    }
/**
 * The PutBox command add boxes to the environment
 * in a starting position and a given name
 */


    private class PutBox implements Command
    {
        String format="PutBox at ([-]*\\d+) ([-]*\\d+) named (\\w+)";
        
        public String getKey(){ return "PutBox";}
        public String getFormat(){return format;}

        public void run() throws Exception {
            // get the variables
            try{
            int xpos=Integer.parseInt(result.group(1));
            int ypos=Integer.parseInt(result.group(2));
            String name=result.group(3);
            // create temp variables
            Position tempos = new Position(xpos,ypos);
            Box tempbox = new Box(name);
            if (bx.getBox(name)!=null)
                throw new Exception ("Error: Box with that name already exists");


            tempbox.setPosition(tempos);

            // do the required action
            // enter a box to the boxes hashmap
            bx.setBox(tempbox,name);
            // user output
            // None
            }catch (Exception ex ) { throw ex;}
        }
        
        
    }
   /**
 * The Assign command create new RunProgram object and add it to the Programs
 * Linked list. it also mark the robot as an assigned robot
 */
    private class Assign implements Command{
        String format="Assign (\\w+) with (.+)";

        public String getKey(){ return "Assign";}
        public String getFormat(){return format;}

        public void run() throws Exception {
            try {
                String name = result.group(1);
                String filename = result.group(2);
                // Check that the robot exists
                if (sim.getRobot(name) == null) {
                    System.out.println("Error: no such robot with this name.");
                    return;
                }
                // check that the program exists
                if (!new File(filename).exists()) {
                    System.out.println("Error: file not found.");
                    return;
                }
                // check that no previous program is assigned to robot
                if (!sim.getRobot(name).getAssign())
                {
                // now that all check are passed, associate program to robot
                // and push it to the Linkedlist in programs
                RunProgram rp = new RunProgram(sim.getRobot(name), filename,bx);
                // mark the robot as assigned
                sim.getRobot(name).setAssign();
                pro.add(rp);
                }
                else
                {
                     throw new Exception("Error: another program is assigned to this robot.");                  
                }
            } catch (FileNotFoundException ex) {
                  Logger.getLogger(Commands.class.getName()).log(Level.SEVERE, null, ex);}
              catch (Exception ex2){ throw ex2;}
            }

    }
    

    public void runCommand(String cmd) throws Exception{
        try {
        String words[]=cmd.split(" ");

        if (hm.get(words[0])==null)
            throw new Exception("Error: command not found.");
        else if (decoder(cmd , hm.get(words[0]).getFormat()))
            hm.get(words[0]).run();
        else throw new Exception("Error: command not found.");
        } catch (Exception ex2){ throw ex2;}

    }
}

